Skip to content

spike: hermetic wasm tool execution via single preopened root (wasi-testsuite#264)#531

Draft
avrabe wants to merge 1 commit into
mainfrom
spike/wasmtime-single-root-hermetic
Draft

spike: hermetic wasm tool execution via single preopened root (wasi-testsuite#264)#531
avrabe wants to merge 1 commit into
mainfrom
spike/wasmtime-single-root-hermetic

Conversation

@avrabe

@avrabe avrabe commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Draft / spike — not for merge. Answers the question from the wasi-testsuite#264 discussion: now that WASI is moving to a single-root filesystem model, can we map Bazel's hermetic file model onto wasmtime and call tools-as-wasm hermetically — the thing the old multi-preopen / absolute-host-path approach couldn't do cleanly?

Answer: yes — and the spike pins down the one Bazel-specific caveat.

What's here

  • transform.rs — a tiny WASI command (wasm32-wasip2 via rust_wasm_binary) that reads→uppercases→writes a file and probes hermeticity (tries to read /etc/hostname,/etc/passwd, exits non-zero if either succeeds).
  • //wasm/private:wasm_tool_run.bzl — experimental rule running the tool as a hermetic Bazel action through one preopened root (wasmtime run --dir root::/).
  • transform_output_testdiff_test, green only if the tool transformed through the single root and the probe was denied.

bazel test //test/spike_wasmtime_hermetic:allpasses (ok: /sample.txt -> /out … sandbox confirmed).

Findings (full write-up in SPIKE.md)

  1. Single-root works; hermeticity is free — intersection of Bazel's sandbox (only declared inputs exist) and WASI's deny-by-default capabilities (guest can't even name a host-absolute path).
  2. The real obstacle — preopening the execroot directly fails with os error 63 = WASI ENOTCAPABLE. Root cause (confirmed two ways): Bazel stages declared inputs as symlinks pointing outside the sandbox, and WASI refuses to traverse a preopen-escaping symlink. wasmtime has no flag to relax this.
  3. Fix — materialize declared inputs as real files inside the root. The preopen directory may itself be a symlink (wasmtime canonicalizes it at open); only files traversed inside the guest must not escape.
  4. Why the old way fought Bazel — absolute-host-path multi-preopen had no stable relocatable paths to hand WASI and pointed at escaping symlinks. One materialized root + guest-absolute paths is the clean shape ci(deps): bump actions/cache from 4 to 5 #264 enables.

Not done (spike scope)

  • Input staging uses run_shell (cp) for brevity — production must use a hermetic copy (copy_to_directory / a launcher) per RULE export\! macro not accessible in rust_wasm_component_bindgen #1, no shell.
  • AOT (.cwasm) + Wizer + a benchmark to back the efficiency claim with a number (native vs JIT vs AOT+wizer).
  • Multiple/structured outputs.

Opening as a draft to capture the finding and the working invocation; happy to turn it into a real wasm_tool_run rule (hermetic staging + AOT/wizer + benchmark) if we want to pursue it.

🤖 Generated with Claude Code

Proof-of-concept answering: with WASI's single-root filesystem direction
(WebAssembly/wasi-testsuite#264), can we map Bazel's hermetic file model onto
wasmtime and call tools-as-wasm hermetically — which the old multi-preopen /
absolute-host-path approach couldn't do cleanly?

Answer: yes. Adds an experimental wasm_tool_run rule (single-root
`wasmtime run --dir root::/`), a tiny wasip2 WASI command that transforms a file
and probes hermeticity, and a diff_test that's green only if the tool read +
wrote through the single root AND its probe of /etc/hostname,/etc/passwd was
denied. `bazel test //test/spike_wasmtime_hermetic:all` passes.

Key finding (SPIKE.md): the single-root model works and hermeticity is the
intersection of Bazel's sandbox and WASI's deny-by-default capabilities. The one
Bazel-specific obstacle: Bazel stages declared inputs as symlinks pointing
outside the sandbox, and WASI refuses to traverse a preopen-escaping symlink
(errno 63 = ENOTCAPABLE; reproduced + root-caused). Fix: materialize inputs as
real files inside the root (the preopen dir itself may be a symlink — wasmtime
canonicalizes it). That is exactly why absolute-host-path multi-preopen fought
Bazel, and why one materialized root + guest-absolute paths is the clean shape.

SPIKE-ONLY: input staging uses run_shell (cp) for brevity; production should use
a hermetic copy (bazel-lib copy_to_directory / a launcher) per RULE #1, plus
AOT(.cwasm)+Wizer and a benchmark for the efficiency claim. Not for merge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant